'''
An RPG Modification for Source games using Source Python.

    Copyright (C) 2013 Steven Hartin

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.

Created on 27 Jan 2013

@author: Steven Hartin
@file: ../sourcerpg/popuplib/popup.py
'''

__all__ = ["popup_players", "EasyMenu"]

from collections import Iterable

from Source import ClientCmd
from messages import ShowMenu
from filters.recipients import get_recipients
from entities.helpers import index_from_edict
from listeners.delays import TickDelays

from sourcerpg.popuplib.option import Option


class PopupPlayer(object):
    def __init__(self, userid):
        self.page = 0
        self.cached_popup = ""
        self.dirty = True
        self.active_menu = None

    def __setattr__(self, attribute, value):
        if attribute == "page" or attribute == "active_menu":
            self.dirty = True
        super(__class__, self).__setattr__(attribute, value)


class PopupPlayers(dict):
    def __init__(self):
        super(__class__, self).__init__()
        # Register the repeat timer to resend all active menus
        TickDelays.delay(4.0, self._tick_callback)

    def _tick_callback(self, *args, **kwargs):
        """Executed when the delay timer is over. Ensure we resend all menus

        Since the OB engine, no matter what we pass into 'timer' for ShowMenu
        usermessage, the menu only stays up for 4 seconds regardless. This is
        to ensure the menu stays up until the user dismisses the menu

        """
        for player_index, popup_player in self.items():
            if popup_player.active_menu is not None:
                popup_player.active_menu.send(player_index)
        # Re-delay for 4 seconds to ensure the menus are sent
        TickDelays.delay(4.0, self._tick_callback)

popup_players = PopupPlayers()


class EasyMenu(object):
    """Temporary class to build and send popups to players

    The reason this is temporary is I needed an API to manage popups but the
    'official' library is still in the works. This should not be feature full
    by any means, just enough to be able to do what I initially need until
    the official library is built and I can switch over to using that

    """
    def __init__(self):
        self.options = []
        self.menu = ShowMenu("")
        self.num_options_per_page = 6
        self.title = ""

    def add_option(self, text, callback, active=True, *args):
        """Adds an option to the end of the popup options

        @param str text The display text for the option
        @param callable callback A callback function to execute when the
            user selects the option
        @param bool active Whether or not the option is selectable
        @param *args Any further args to pass to the callback

        """
        self.options.append(Option(text, callback, active, *args))

    def get_options(self, page):
        """Obtain a list of options which appear on a specified page

        @param int page The page to obtain the list of options for
        @return list A list of options which appear on the page

        """
        start_index = page * self.num_options_per_page
        end_index = (page + 1) * self.num_options_per_page
        end_index = min(end_index, len(self.options))
        options = self.options[start_index: end_index]
        return options

    def send(self, users):
        """Sends this popup to a player

        @param int users A tuple of player indexes to send to

        """
        if not isinstance(users, Iterable):
            users = (users, )
        for user in users:
            if user not in popup_players:
                popup_players[user] = PopupPlayer(user)
            if popup_players[user].active_menu != self:
                popup_players[user].active_menu = self
            if popup_players[user].dirty is True:
                popup_players[user].active_menu = self
                page = popup_players[user].page
                options = self.get_options(page)
                popup_string = "%s\n%s\n" % (self.title, "-" * 25)
                del self.menu.slots[:]
                for index, option in enumerate(options):
                    option_string = ""
                    if option.active is True:
                        self.menu.slots.append(index)
                        option_string += "->"
                    option_string += "%d. %s\n" % (index + 1, option.text)
                    popup_string += option_string
                popup_string += ("-" * 25 + "\n")
                if page > 0:
                    popup_string += "->7. Back"
                    self.menu.slots.append(6)
                popup_string += " \n"
                if (page + 1) * self.num_options_per_page < len(self.options):
                    popup_string += "->8. Next"
                    self.menu.slots.append(7)
                self.menu.slots.append(8)
                popup_string += " \n->9. Exit\n"
                self.menu.message = popup_string
                self.menu.duration = 600
                self.menu.send(get_recipients(user))
                popup_players[user].cached_popup = popup_string
                popup_players[user].dirty = False
            else:
                self.menu.message = popup_players[user].cached_popup
                self.menu.send(get_recipients(user))

    def handle_choice(self, user, choice):
        """Handles the input from the user. Ensure the right path is taken

        @param int user The index of the user to run choose an option
        @param int choice The index of the option which was selected

        """
        options = self.get_options(popup_players[user].page)
        if choice <= len(options):
            # We chose a valid option, call the callback
            option = options[choice - 1]
            option.callback(user, options, option.args)
            popup_players[user].active_menu = None
        elif choice == 7:
            # We chose the back option, go back a page
            if popup_players[user].page > 0:
                popup_players[user].page -= 1
            self.send(user)
        elif choice == 8:
            # We chose the next option, go forward a page
            next_page = popup_players[user].page + 1
            next_page_option_index = next_page * self.num_options_per_page
            if next_page_option_index < len(self.options):
                popup_players[user].page = next_page
            self.send(user)
        elif choice == 9:
            popup_players[user].active_menu = None


def client_command(entity, command):
    """Executed when the 'menuselect' client command is issued from a client

    @param int entity The edict object of the entity which issued the command
    @param ClientCmd command The command object which contains the args passed

    """
    index = index_from_edict(entity)
    if index in popup_players:
        if popup_players[index].active_menu is not None:
            # There should be a single arg, an int value of the option
            # selected
            args = command.ArgS().split()
            choice = None
            if args:
                choice = int(args[0])
            popup_players[index].active_menu.handle_choice(index, choice)


def unhook_commands():
    # Unregister the client command
    ClientCmd.GetClientCommand("menuselect").Remove(client_command)

# Register the client command
ClientCmd.GetClientCommand("menuselect").AddToEnd(client_command)
